package cn.dansj.configure;

import cn.dansj.common.utils.exception.PageNotFoundException;
import cn.dansj.common.utils.exception.TokenParseException;
import cn.dansj.common.utils.json.DictMap;
import cn.dansj.bean.InterfaceResponse;
import cn.dansj.enums.GlobalEnv;
import cn.dansj.common.utils.enums.ResponseCode;
import cn.dansj.exception.AuthenticationException;
import cn.dansj.common.utils.crypt.EncryptionAndDecryptionException;
import cn.dansj.common.request.RequestUtils;
import cn.dansj.common.utils.lock.LockException;
import cn.dansj.common.utils.transfer.StringUtils;
import cn.dansj.common.utils.transfer.Transformation;
import cn.dansj.common.utils.bean.Return;
import cn.dansj.common.utils.transfer.Verification;
import cn.dansj.exception.InterfaceResponseException;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.catalina.connector.ClientAbortException;
import cn.dansj.utils.project.ProjectUtils;
import org.apache.hc.client5.http.HttpHostConnectException;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.core.MethodParameter;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.sql.SQLSyntaxErrorException;

/**
 * 统一异常处理
 * 返回值的统一处理
 * /error的返回处理
 */
@RestControllerAdvice(basePackages = "cn.dansj.controller", basePackageClasses = UnifiedControllerExceptionHandler.class)
public class UnifiedControllerExceptionHandler implements ResponseBodyAdvice<Object> {
    private final Logger log = LoggerFactory.getLogger(getClass());

    @Override
    public boolean supports(@NotNull MethodParameter returnType, @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
        return returnType.getParameterType().equals(Return.class) || returnType.getParameterType().equals(InterfaceResponse.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {
        if (body == null) return null;
        final DictMap returnData = DictMap.parseObject(body);
        // 未开启分页
        if (!Transformation.castToBoolean(returnData.get("curPage"))) {
            returnData.remove("curPage");
            returnData.remove("totalPage");
            returnData.remove("pageSize");
            returnData.remove("curRecords");
        }
        // 失败状态
        if (!returnData.getBooleanValue("status", false)) {
            returnData.remove("sourceCode");
            returnData.remove("totalRecords");
            returnData.remove("version");
        }
        return returnData;
    }

    @RestController
    static class ErrorMapping implements ErrorController {
        @RequestMapping("/error")
        public void handleError(HttpServletRequest request) {
            final int statusCode = Transformation.parseInt(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE), 500);
            HttpStatus httpStatus = Transformation.lambdaTryCatch(() -> HttpStatus.valueOf(statusCode), () -> HttpStatus.INTERNAL_SERVER_ERROR);
            final String error = GlobalEnv.ErrorCodeMapping.getString(Transformation.nvl(Transformation.toString(httpStatus.value()), "500"));
            RequestUtils.response(httpStatus.value(), Verification.checkNull(error) ? httpStatus.getReasonPhrase() : error);
        }
    }

    @ExceptionHandler
    public Return<?> DataIntegrityViolationException(DataIntegrityViolationException exception) {
        String[] strings = StringUtils.split(exception.fillInStackTrace().getMessage(), ":");
        return Return.T(false, "数据写入失败:" + strings[strings.length - 1]).setResponseCode(500);
    }

    @ExceptionHandler
    public Return<?> CannotGetJdbcConnectionException(CannotGetJdbcConnectionException exception) {
        log.error("连接异常:{}", ProjectUtils.getRequestUrl(), exception);
        if (Transformation.getBaseException(exception).getMessage().equals("Connection refused (Connection refused)")) {
            return Return.T(false, "连接被拒绝,请检查连接信息").setResponseCode(500);
        }
        return Return.T(false, "连接异常,请检查连接信息").setResponseCode(500);
    }

    @ExceptionHandler
    public Return<?> exception(Exception exception) {
        log.error("未解析异常\n请求地址：\n{}\n异常信息：", ProjectUtils.getRequestUrl(), exception);
        return Return.T(false, "请求失败, 请联系管理员").setResponseCode(500);
    }

    @ExceptionHandler
    public Return<?> authenticationException(AuthenticationException authenticationException) {
        log.error("权限异常", authenticationException);
        return Return.T(false, "无权限访问").setResponseCode(401);
    }

    @ExceptionHandler
    public Return<?> arrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
        log.error("索引超出边界:{}", arrayIndexOutOfBoundsException.getMessage(), arrayIndexOutOfBoundsException);
        return Return.T(false, "请求失败").setResponseCode(500);
    }

    @ExceptionHandler
    public Return<?> lockException(LockException lockException) {
        log.error("获取锁失败：{}", ProjectUtils.getRequestUrl(), lockException);
        return Return.T(false, "请求失败").setResponseCode(500);
    }

    @ExceptionHandler(RedisSystemException.class)
    public Return<?> redisSystemException() {
        log.error("redis连接断开:{}", ProjectUtils.getRequestUrl());
        return Return.T(false, "redis连接断开");
    }

    @ExceptionHandler(AsyncRequestTimeoutException.class)
    @ResponseStatus(HttpStatus.GATEWAY_TIMEOUT)
    public Return<?> asyncRequestTimeoutException() {
        return Return.T(false, "Request Timeout").setResponseCode(504);
    }

    @ExceptionHandler
    public Return<?> socketException(SocketException socketException) {
        log.error("socket异常：{}", ProjectUtils.getRequestUrl(), socketException);
        return Return.T(false, "socket请求失败").setResponseCode(500);
    }

    @ExceptionHandler
    public Return<?> queryTimeoutException(QueryTimeoutException queryTimeoutException) {
        log.error("查询超时：{}", ProjectUtils.getRequestUrl(), queryTimeoutException);
        return Return.T(false, "请求失败").setResponseCode(500);
    }

    @ExceptionHandler
    public Return<?> connectException(ConnectException connectException) {
        log.error("连接异常：{}", ProjectUtils.getRequestUrl(), connectException);
        return Return.T(false, "请求失败").setResponseCode(500);
    }

    @ExceptionHandler
    public Return<?> clientAbortException(ClientAbortException clientAbortException) {
        log.error("客户端abort：{}", String.valueOf(clientAbortException));
        return Return.T(false, "请求失败");
    }

    @ExceptionHandler
    public Return<?> ioException(IOException ioException) {
        log.error("io异常：{}", ProjectUtils.getRequestUrl(), ioException);
        return Return.T(false, "请求失败");
    }

    @ExceptionHandler
    public Return<?> illegalArgumentException(IllegalArgumentException illegalArgumentException) {
        log.error("illegalArgumentException error error:{} url:{}", illegalArgumentException.getMessage(), ProjectUtils.getRequestUrl());
        return Return.T(false, "请求失败");
    }

    @ExceptionHandler
    public Return<?> illegalStateException(IllegalStateException illegalStateException) {
        log.error("illegalStateException error:{} url:{}", illegalStateException.getMessage(), ProjectUtils.getRequestUrl());
        return Return.T(false, "请求失败");
    }

    @ExceptionHandler
    public Return<?> noResourceFoundException(NoResourceFoundException noResourceFoundException) {
        log.error("noResourceFoundException error:{} url:{}", noResourceFoundException.getMessage(), ProjectUtils.getRequestUrl());
        return Return.T(false, "请求失败");
    }

    @ExceptionHandler
    public Return<?> servletRequestBindingException(ServletRequestBindingException servletRequestBindingException) {
        log.error("servletRequestBindingException error:{} url:{}", servletRequestBindingException.getMessage(), ProjectUtils.getRequestUrl());
        return Return.T(false, "缺少参数[" + Transformation.patternGet(servletRequestBindingException.getMessage(), "'(.*?)'", 1) + "]").setResponseCode(ResponseCode.ParamExistNull.getStatusCode());
    }

    @ExceptionHandler
    public Return<?> encryptionAndDecryptionException(EncryptionAndDecryptionException encryptionAndDecryptionException) {
        log.error("encryptionAndDecryptionException error:{} url:{}", encryptionAndDecryptionException.getMessage(), ProjectUtils.getRequestUrl());
        return Return.T(false, "请求失败").setResponseCode(ResponseCode.RunTimeError.getStatusCode());
    }

    @ExceptionHandler
    public Return<?> multipartException(MultipartException multipartException) {
        log.error("multipartException error:{} url:{}", multipartException.getMessage(), ProjectUtils.getRequestUrl());
        return Return.T(false, "请求失败").setResponseCode(ResponseCode.RunTimeError.getStatusCode());
    }

    @ExceptionHandler
    public Return<?> badSqlGrammarException(BadSqlGrammarException badSqlGrammarException, HttpServletRequest request) {
        String exceptionMsg = Transformation.translateException(Transformation.getBaseException(badSqlGrammarException).getMessage());
        log.error("badSqlGrammarException error:{} url:{}", exceptionMsg, ProjectUtils.getRequestUrl());
        return Return.T(false, request.getRequestURI().equals("/data/sql") ? exceptionMsg : "sql执行出错").setResponseCode(ResponseCode.RunTimeError.getStatusCode());
    }

    private static final DictMap NexusStatusCode = DictMap.of("403", "权限不足", "404", "资源不存在", "422", "ID不属于该类型");

    @ExceptionHandler
    public Return<?> unprocessableEntity(HttpClientErrorException.UnprocessableEntity unprocessableEntity) {
        log.error("unprocessableEntity error:{} url:{}", unprocessableEntity.getMessage(), ProjectUtils.getRequestUrl());
        return Return.T(false, Transformation.nvl(NexusStatusCode.getString(String.valueOf(unprocessableEntity.getStatusCode().value())), "删除失败"));
    }

    @ExceptionHandler
    public Return<?> badRequest(HttpClientErrorException.BadRequest badRequest) {
        log.error("badRequest error:{} url:{}", badRequest.getMessage(), ProjectUtils.getRequestUrl());
        return Return.T(false, badRequest.getLocalizedMessage());
    }

    @ExceptionHandler
    public Return<?> resourceAccessException(ResourceAccessException resourceAccessException) {
        log.error("resourceAccessException error:{} url:{}", resourceAccessException.getMessage(), ProjectUtils.getRequestUrl());
        final Throwable baseException = Transformation.getBaseException(resourceAccessException);
        if (baseException instanceof HttpHostConnectException httpHostConnectException) {
            return Return.T(false, httpHostConnectException.getHost() + "连接失败");
        }
        return Return.T(false, Transformation.getBaseException(resourceAccessException).getMessage());
    }

    @ExceptionHandler(PageNotFoundException.class)
    public void pageNotFoundException() {
        RequestUtils.response404();
    }

    @ExceptionHandler
    public void interfaceResponseException(InterfaceResponseException interfaceResponseException) {
        RequestUtils.response(interfaceResponseException.getErrorCode(), interfaceResponseException.getMessage());
    }

    @ExceptionHandler
    public void httpServerErrorException(HttpServerErrorException httpServerErrorException) {
        RequestUtils.response(httpServerErrorException.getStatusCode().value(), httpServerErrorException.getStatusText());
    }

    @ExceptionHandler
    public Return<?> multipleCompilationErrorsException(MultipleCompilationErrorsException multipleCompilationErrorsException) {
        log.error("groovy脚本编译失败异常：{}, url：{}", multipleCompilationErrorsException.getMessage(), ProjectUtils.getRequestUrl() + (RequestUtils.fromDispatcher().isEmpty() ? "" : "@" + RequestUtils.fromDispatcher()));
        return Return.T(false, "脚本编译失败").setResponseCode(500);
    }

    @ExceptionHandler
    public Return<?> UndeclaredThrowableException(UndeclaredThrowableException undeclaredThrowableException) {
        final Throwable baseException = Transformation.getBaseException(undeclaredThrowableException);
        if (baseException instanceof SocketTimeoutException socketTimeoutException) {
            log.error("socketTimeoutException\n请求地址：\n{}\n异常信息：", ProjectUtils.getRequestUrl(), socketTimeoutException);
            return Return.T(false, "连接超时");
        }
        if (baseException instanceof SQLSyntaxErrorException sqlSyntaxErrorException) {
            final String message = Transformation.nvl(Transformation.patternGet(sqlSyntaxErrorException.getMessage(), "Table '(.*?)' doesn't exist"), "SQL语法错误, 调整后重新执行");
            return Return.T(false, message).setResponseCode(ResponseCode.RunTimeError.getStatusCode());
        }
        if (baseException instanceof HttpHostConnectException httpHostConnectException) {
            final String message = httpHostConnectException.getMessage().contains("Connection refused: no further information") ? "连接被拒绝, 请检查服务是否在线" : "连接请求异常";
            return Return.T(false, message).setResponseCode(ResponseCode.RunTimeError.getStatusCode());
        }
        log.error("未定义异常\n请求地址：\n{}\n异常信息：", ProjectUtils.getRequestUrl(), undeclaredThrowableException.getCause());
        return Return.T(false, "请求失败, 请联系管理员").setResponseCode(500);
    }

    @ExceptionHandler
    public void tokenParseException(TokenParseException tokenParseException) {
        RequestUtils.response401(tokenParseException.getMessage());
    }
}
